Ejemplo de creación de un nuevo tipo de objeto

En gap todo son objetos. Podemos crear nuestros propios objetos con sus atributos, propiedades y métodos.

Vamos a ver en este bloc un ejemplo de un objeto que represente un número racional. Esto ya existe en gap pero lo hacemos desde cero para ilustrar cómo se define una nueva clase.

Todas las deficiones están en ejemplo-objeto.g, vamos a ir comentanto poco a poco lo que contiene este fichero.

Read("ejemplo-objeto.g");

Creando el tipo de representación interna y la categoría del objeto

Vamos a representar una fracción mediante su numerador y denominador. Es por ello que vamos a usar una representación nueva, EsFraccionRep, del tipo IsAttributeStoringRep, pues tendremos estos dos atributos almacenados en un racional. A la categoría (clase) le vamos a llamar EsFraccion y va a ser representable mediante EsFraccionRep y es una subcategoría de IsAdditiveElement, pues luego vamos a definir una función que sume fracciones. Por último definimos un nuevo tipo de dato que llamaremos TipoFraccion, que estará en la familia FamiliaFracciones.

DeclareRepresentation("EsFraccionRep", IsAttributeStoringRep, []);
DeclareCategory( "EsFraccion", EsFraccionRep and IsAdditiveElement);
TipoFraccion:=NewType(NewFamily("FamiliaFracciones"), EsFraccion);

Declarando los atributos, propiedades y métodos

Vamos a tener como atributos el numerador y el denominador.

DeclareAttribute("Numerador", EsFraccion);
DeclareAttribute("Denominador", EsFraccion);

Como operaciones vamos a definir una simplificación y una suma (que podemos hacerlo al pertenecer nuestra categeoría a la de los elementos con suma). La suma no hace falta declararla, sólo tendremos que definir después un método apropiado.

DeclareOperation("Simplifica", [EsFraccion]);

Vamos además a definir dos propiedades: ser positivo y ser no negativo.

DeclareProperty("EsPositivo", EsFraccion);
DeclareProperty("EsNoNegativo", EsFraccion);

Creando un objeto de tipo EsFraccion

Vamos a dar una función que, a partir de dos enteros, nos devuelva una fracción.

Fraccion:=function(a,b)
    local r;
    if not(IsInt(a)) then 
        Error("El primer argumento debe ser un entero");
    fi;

    if not(IsInt(b)) or (b=0) then 
        Error("El segundo argumento debe ser un entero no nulo");
    fi;
    
    r:=Objectify(TipoFraccion, rec());
    SetNumerador(r,a);
    SetDenominador(r,b);
    return r;
end;
r:=Fraccion(2,4);
2 / 4
TypeObj(r);
<Type: (FamiliaFracciones, [ IsComponentObjectRep, IsAttributeStoringRep, IsExtAElement, ... ]), data: fail,>
EsFraccion(r);
true

Como podemos observar, al crear r devuelve 2 / 4. Ésta es la forma en la que le hemos dicho que represente r, y lo que está devolviendo es el objeto r. Para ello hemos definido un método ViewString para fracciones.

InstallMethod(ViewString, "mostrar fracciones", [EsFraccion],
    function(x)
        return Concatenation(String(Numerador(x))," / ",String(Denominador(x)));
end);

Para la salida de Print podemos definir String.

InstallMethod(String, "fracciones a cadenas", [EsFraccion],
    function(x)
        return Concatenation(String(Numerador(x)),"/",String(Denominador(x)));
end);
Print(r);
2/4

Como la función Fraccion establece los atributos denominador y numerador, ya podemos pedirle a gap que nos los diga.

Numerador(r);
2
Denominador(r);
4

También hemos definido otra forma “más bonita” de representar r. Para ello hemos definido el método Display para fracciones.

InstallMethod(Display, "mostrar racionales", [EsFraccion],
    function(x)
        local l,s,i;

        l:=Maximum(Length(String(Numerador(x))),Length(String(Denominador(x))));
        s:="";
        for i in [1..l] do
            Append(s,"-");
        od;
        Print(Numerador(x),"\n");
        Print(s,"\n");
        Print(Denominador(x),"\n");
        return;
end);
Display(r);
2
-
4

Las propiedades ser positivo o no negativo se pueden definir como sigue.

InstallMethod(EsPositivo, "es positivo para fracciones", [EsFraccion],
    function(x)
        return Numerador(x)*Denominador(x)>0;
end);

InstallMethod(EsNoNegativo, "es positivo para fracciones", [EsFraccion],
    function(x)
        return Numerador(x)*Denominador(x)>=0;
end);

El hecho de que una fracción sea positiva implica que ésta es no negativa. Podemos por tanto indicarle esto a gap para que siempre que sepa que una fracción es positiva y le preguntemos si es negativa, no tenga que hacer ningún cálculo intermedio.

InstallTrueMethod(EsNoNegativo, EsPositivo);
TraceMethods([EsNoNegativo]);
r:=Fraccion(2,4);
2 / 4
EsNoNegativo(r);
#I  EsNoNegativo: es positivo para fracciones at ejemplo-objeto.g:31
true

Como EsNoNegativo es una propiedad de r, si volvemos a preguntar si es no negativo no hará ninguna llamada a ninguna función, pues ya lo tiene almacenado.

EsNoNegativo(r);
true

Lo mismo ocurre si primero hemos preguntado si es positivo. Le hemos dicho que eso implica ser no negativo.

r:=Fraccion(2,4);
2 / 4
EsPositivo(r);
true
EsNoNegativo(r);
true
UntraceMethods([EsNoNegativo]);

Definamos ahora una función que devuelva la fracción simplificada de una fracción dada.

InstallMethod(Simplifica, "simplificación para fracciones", [EsFraccion], 
    function(x)
        local s, d;

        d:=Gcd(Numerador(x),Denominador(x));
        return Fraccion(Numerador(x)/d,Denominador(x)/d);
end);
Simplifica(r);
1 / 2
r;
2 / 4

La igualdad de fracciones y la suma se pueden definir de la siguiente manera.

InstallMethod(\=, "igualdad para fracciones", [EsFraccion, EsFraccion],
    function(x,y)
        return Numerador(x)*Denominador(y)=Numerador(y)*Denominador(x); 
end);

InstallMethod(\+, "suma de fracciones", [EsFraccion, EsFraccion],
    function(x,y)
        return Simplifica(Fraccion(Numerador(x)*Denominador(y)+ Numerador(y)*Denominador(x),Denominador(x)*Denominador(y)));
end);
Simplifica(r)=r;
true
r+r;
1 / 1